%%--------------------------------------------------------------------
%% Copyright (c) 2017-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------

-module(emqx_app).

-behaviour(application).

-export([ start/2
        , prep_stop/1
        , stop/1
        , get_description/0
        , get_release/0
        ]).

%% internal exports for ad-hoc debugging.
-export([ set_alias_enrichment_module/0
        , set_special_auth_module/0
        ]).

-define(APP, emqx).

-include("emqx_release.hrl").

%%--------------------------------------------------------------------
%% Application callbacks
%%--------------------------------------------------------------------

start(_Type, _Args) ->
    set_backtrace_depth(),
    print_otp_version_warning(),
    print_banner(),
    %% Load application first for ekka_mnesia scanner
    _ = load_ce_modules(),
    ekka:start(),
    {ok, Sup} = emqx_sup:start_link(),
    ok = start_autocluster(),
    %% We need to make sure that emqx's listeners start before plugins
    %% and modules. Since if the emqx-conf module/plugin is enabled, it will
    %% try to start or update the listeners with the latest configuration
    emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
    ok = emqx_plugins:init(),
    _ = emqx_plugins:load(),
    _ = start_ce_modules(),
    set_alias_enrichment_module(),
    _ = set_special_auth_module(),
    register(emqx, self()),
    print_vsn(),
    {ok, Sup}.

prep_stop(_State) ->
    ok = emqx_alarm_handler:unload(),
    emqx_boot:is_enabled(listeners)
      andalso emqx_listeners:stop().

stop(_State) ->
    ok.

set_backtrace_depth() ->
    Depth = application:get_env(?APP, backtrace_depth, 16),
    _ = erlang:system_flag(backtrace_depth, Depth),
    ok.

-ifndef(EMQX_ENTERPRISE).
load_ce_modules() ->
    application:load(emqx_modules).
start_ce_modules() ->
    application:ensure_all_started(emqx_modules).
-else.
load_ce_modules() ->
    ok.
start_ce_modules() ->
    ok.
-endif.

set_alias_enrichment_module() ->
    case emqx:get_env(alias_enrichment_module) of
        undefined ->
            ok;
        Mod ->
            case erlang:function_exported(Mod, enrich_with_aliases, 2) of
                true ->
                    persistent_term:put(alias_enrichment_module, Mod);
                false ->
                    ok
            end
    end.

set_special_auth_module() ->
    case emqx:get_env(special_auth_module) of
        undefined ->
            ok;
        Mod ->
            case erlang:function_exported(Mod, check_authn, 2) of
                true ->
                    Priority = authn_module_priority(Mod),
                    persistent_term:put(special_auth_module, Mod),
                    emqx:hook('client.authenticate', fun Mod:check_authn/2, Priority);
                false ->
                    ok
            end
    end.

authn_module_priority(Mod) ->
    try
        Mod:authn_priority()
    catch
        _:_ ->
            10_000
    end.

%%--------------------------------------------------------------------
%% Print Banner
%%--------------------------------------------------------------------

-if(?OTP_RELEASE> 22).
print_otp_version_warning() -> ok.
-else.
print_otp_version_warning() ->
    io:format("WARNING: Running on Erlang/OTP version ~p. Recommended: 23~n",
              [?OTP_RELEASE]).
-endif. % OTP_RELEASE

-ifndef(TEST).

print_banner() ->
    io:format("Starting ~s on node ~s~n", [?APP, node()]).

print_vsn() ->
    io:format("~s ~s is running now!~n", [get_description(), get_release()]).

-else. % TEST

print_vsn() ->
    ok.

print_banner() ->
    ok.

-endif. % TEST

get_description() ->
    {ok, Descr0} = application:get_key(?APP, description),
    case os:getenv("EMQX_DESCRIPTION") of
        false -> Descr0;
        "" -> Descr0;
        Str -> string:strip(Str, both, $\n)
    end.

get_release() ->
    case lists:keyfind(emqx_vsn, 1, ?MODULE:module_info(compile)) of
        false ->    %% For TEST build or depedency build.
            release_in_macro();
        {_, VsnCompile} -> %% For emqx release build
            VsnMacro = release_in_macro(),
            case string:str(VsnCompile, VsnMacro) of
                1 -> ok;
                _ -> error({version_not_match, VsnCompile, VsnMacro})
            end,
            VsnCompile
    end.

release_in_macro() ->
    element(2, ?EMQX_RELEASE).

%%--------------------------------------------------------------------
%% Autocluster
%%--------------------------------------------------------------------
start_autocluster() ->
    ekka:callback(prepare, fun emqx:shutdown/1),
    ekka:callback(reboot,  fun emqx:reboot/0),
    _ = ekka:autocluster(?APP), %% returns 'ok' or a pid or 'any()' as in spec
    ok.
